package com.mine.common.core.utils.datetime;

import java.time.*;
import java.time.format.DateTimeFormatter;
import java.time.temporal.TemporalAdjusters;
import java.time.temporal.WeekFields;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;

/**
 * 日期
 *
 * @author gongming
 * @date 2023/9/7 14:41
 */
public class Dates implements DateTime {

    /**
     * 获取到毫秒级时间戳
     *
     * @param localDateTime 具体时间
     * @return long 毫秒级时间戳
     */
    public static long toEpochMilli(LocalDateTime localDateTime) {
        return localDateTime.toInstant(ZoneOffset.of("+8")).toEpochMilli();
    }

    /**
     * 毫秒级时间戳转 LocalDateTime
     *
     * @param epochMilli 毫秒级时间戳
     * @return LocalDateTime
     */
    public static LocalDateTime ofEpochMilli(long epochMilli) {
        return LocalDateTime.ofInstant(Instant.ofEpochMilli(epochMilli), ZoneOffset.of("+8"));
    }

    public static long toEpochSecond() {
        return toEpochSecond(LocalDateTime.now());
    }

    /**
     * 获取到秒级时间戳
     *
     * @param localDateTime 具体时间
     * @return long 秒级时间戳
     */
    public static long toEpochSecond(LocalDateTime localDateTime) {
        return localDateTime.toEpochSecond(ZoneOffset.of("+8"));
    }

    /**
     * 秒级时间戳转 LocalDateTime
     *
     * @param epochSecond 秒级时间戳
     * @return LocalDateTime
     */
    public static LocalDateTime ofEpochSecond(long epochSecond) {
        return LocalDateTime.ofEpochSecond(epochSecond, 0, ZoneOffset.of("+8"));
    }

    /**
     * Date时间类转LocalDateTime
     *
     * @param date Date时间类
     * @return LocalDateTime
     */
    public static LocalDateTime ofDate(Date date) {
        return date.toInstant().atOffset(ZoneOffset.of("+8")).toLocalDateTime();
    }

    /**
     * LocalDateTime时间类转 Date时间类
     *
     * @param localDateTime LocalDateTime时间类
     * @return Date时间类
     */
    public static Date toDate(LocalDateTime localDateTime) {
        return Date.from(localDateTime.atZone(ZoneOffset.of("+8")).toInstant());
    }

    public static boolean compareDateTime(DateTimeType type, String former, String latter) {
        return compareDateTime(type, parse(type, former), parse(type, latter));
    }

    public static boolean compareDateTime(DateTimeType type, LocalDateTime former, LocalDateTime latter) {
        return firstDateTime(type, former).isEqual(firstDateTime(type, latter));
    }

    public static LocalDateTime plusOf(DateTimeType type, LocalDateTime dateTime) {
        switch (type) {
            case YEAR:
                return dateTime.plusYears(1L);
            case MONTH:
                return dateTime.plusMonths(1L);
            case WEEK:
                return dateTime.plusWeeks(1L);
            case DAY:
                return dateTime.plusDays(1L);
            case HOUR:
                return dateTime.plusHours(1L);
            case MINUTE:
                return dateTime.plusMinutes(1L);
            case SECOND:
                return dateTime.plusSeconds(1L);
            default:
                throw new IllegalStateException("Not supported value: " + type);
        }
    }

    public static LocalDateTime minusOf(DateTimeType type, LocalDateTime dateTime) {
        switch (type) {
            case YEAR:
                return dateTime.minusYears(1L);
            case MONTH:
                return dateTime.minusMonths(1L);
            case WEEK:
                return dateTime.minusWeeks(1L);
            case DAY:
                return dateTime.minusDays(1L);
            case HOUR:
                return dateTime.minusHours(1L);
            case MINUTE:
                return dateTime.minusMinutes(1L);
            case SECOND:
                return dateTime.minusSeconds(1L);
            default:
                throw new IllegalStateException("Not supported value: " + type);
        }
    }

    public static DateTimeType fitDateTimeType(DateTimeType type, String beginDateTime, String endDateTime) {
        return fitDateTimeType(type, parse(type, beginDateTime), parse(type, endDateTime));
    }

    /**
     * 降级适配器
     *
     * @param type          原类型 = 按照某个类型间隔时间差
     * @param beginDateTime 开始时间
     * @param endDateTime   结束时间
     * @return {@link DateTimeType}
     */
    public static DateTimeType fitDateTimeType(DateTimeType type, LocalDateTime beginDateTime, LocalDateTime endDateTime) {
        final String startStr = fitDateTimeFormat(type, beginDateTime),
                overStr = fitDateTimeFormat(type, (endDateTime == null) ? beginDateTime : endDateTime);

        // 校验一下，传入类型
        if (!startStr.equals(overStr) || type == DateTimeType.SECOND) {
            return type;
        }

        return DateTimeType.nextDateTimeType(type);
    }

    public static String fitDateTimeFormat(DateTimeType type, LocalDateTime dateTime) {
        checkLocalDateTime(dateTime);
        switch (type) {
            case YEAR:
                return String.valueOf(dateTime.getYear());
            case MONTH:
                return parseOfCustomFormat(dateTime, "yyyy-MM");
            case WEEK:
                return dateTime.getYear() + "-" + dateTime.get(WeekFields.ISO.weekOfWeekBasedYear());
            case DAY:
                return parseOfCustomFormat(dateTime, "yyyy-MM-dd");
            case HOUR:
                return parseOfCustomFormat(dateTime, "yyyy-MM-dd HH");
            case MINUTE:
                return parseOfCustomFormat(dateTime, "yyyy-MM-dd HH:mm");
            case SECOND:
                return parseOfCustomFormat(dateTime, "yyyy-MM-dd HH:mm:ss");
            default:
                throw new IllegalStateException("Not supported value: " + type);
        }
    }

    private static void checkLocalDateTime(LocalDateTime dateTime) {
        if (dateTime == null) {
            throw new RuntimeException("日期时间不允许为空");
        }
    }

    public static String parseOfStandardFormat(LocalDateTime dateTime) {
        return parseOfCustomFormat(dateTime, STANDARD_FORMAT);
    }

    public static String parseOfCustomFormat(LocalDateTime dateTime, String customFormat) {
        return dateTime.format(DateTimeFormatter.ofPattern(customFormat));
    }

    public static String getHashKeyStr(DateTimeType type, String first, String last) {
        // 特殊，周 -> 是否在同一周
        return type.name() + replaceSymbolNumber(fitDateTimeFormat(type, parse(type, first)) + fitDateTimeFormat(type, parse(type, last)));
    }

    /**
     * 替换符号编号
     * "2023-09-25 14:25:29" - 去除与替换：["-", " ", ":"] = 20230925142529
     *
     * @param dateTimeStr str
     * @return {@link String}
     */
    private static String replaceSymbolNumber(String dateTimeStr) {
        return dateTimeStr.replaceAll("-", "").replaceAll(" ", "").replaceAll(":", "");
    }

    public static LocalDateTime parse(DateTimeType type, String dateTimeStr) {
        try {
            return LocalDateTime.parse(fitParse(type, dateTimeStr), DateTimeFormatter.ofPattern(STANDARD_FORMAT));
        } catch (Exception e) {
            throw new RuntimeException("Please use date time for parsing ：[" + STANDARD_FORMAT + "], exception -> " + e.getMessage());
        }
    }

    private static String fitParse(DateTimeType type, String dateTimeStr) {
        checkDateTimeStr(dateTimeStr);

        switch (type) {
            case YEAR:
                return replaceParameter(type, dateTimeStr).concat("-01-01 00:00:00");
            case MONTH:
                return replaceParameter(type, dateTimeStr).concat("-01 00:00:00");
            case WEEK:
            case DAY:
                return replaceParameter(type, dateTimeStr).concat(" 00:00:00");
            case HOUR:
                return replaceParameter(type, dateTimeStr).concat(":00:00");
            case MINUTE:
                return replaceParameter(type, dateTimeStr).concat(":00");
            case SECOND:
                return replaceParameter(type, dateTimeStr);
            default:
                throw new IllegalStateException("Not supported value: " + type);
        }
    }

    private static void checkDateTimeStr(String dateTimeStr) {
        if (dateTimeStr == null || dateTimeStr.trim().length() < MIN_LENGTH) {
            throw new IllegalArgumentException("String dateTimeStr -> is not null or length() < " + MIN_LENGTH);
        }
    }

    protected static String replaceParameter(DateTimeType type, String dateTimeStr) {
        switch (type) {
            case YEAR:
                return replaceParameter(type, dateTimeStr, "yyyy");
            case MONTH:
                return replaceParameter(type, dateTimeStr, "yyyy-MM");
            case WEEK:
            case DAY:
                return replaceParameter(type, dateTimeStr, "yyyy-MM-dd");
            case HOUR:
                return replaceParameter(type, dateTimeStr, "yyyy-MM-dd HH");
            case MINUTE:
                return replaceParameter(type, dateTimeStr, "yyyy-MM-dd HH:mm");
            case SECOND:
                return replaceParameter(type, dateTimeStr, "yyyy-MM-dd HH:mm:ss");
            default:
                throw new IllegalStateException("Not supported value: " + type);
        }
    }

    protected static String replaceParameter(DateTimeType type, String dateTimeStr, String fitFormat) {
        if (dateTimeStr.length() < fitFormat.length()) {
            throw new IllegalArgumentException(String.format("Sorry, %s format[ %s ]，is not fit[ %s ]%n",
                    type.name(), dateTimeStr, fitFormat));
        }
        return dateTimeStr.substring(0, fitFormat.length());
    }

    public static LocalDateTime firstDateTime(DateTimeType type, String dateTime) {
        return firstDateTime(type, parse(type, dateTime));
    }

    public static LocalDateTime firstDateTime(DateTimeType type, LocalDateTime dateTime) {
        switch (type) {
            case YEAR:
                return firstDateTime(DateTimeType.DAY, dateTime).with(TemporalAdjusters.firstDayOfYear());
            case MONTH:
                return firstDateTime(DateTimeType.DAY, dateTime).with(TemporalAdjusters.firstDayOfMonth());
            case WEEK:
                return firstDateTime(DateTimeType.DAY, dateTime).with(DayOfWeek.MONDAY);
            case DAY:
                return dateTime.toLocalDate().atStartOfDay();
            case HOUR:
                return firstDateTime(DateTimeType.MINUTE, dateTime.withMinute(0));
            case MINUTE:
                return firstDateTime(DateTimeType.SECOND, dateTime.withSecond(0));
            case SECOND:
                return dateTime.withNano(0);
            default:
                throw new IllegalStateException("Not supported value: " + type);
        }
    }

    /**
     * 确保是我们需要的格式时间
     *
     * @param type     类型
     * @param dateTime 时间
     * @return 时间节点末值 23:59:59.999_999_999
     */
    public static LocalDateTime lastDateTime(DateTimeType type, String dateTime) {
        return lastDateTime(type, parse(type, dateTime));
    }

    public static LocalDateTime lastDateTime(DateTimeType type, LocalDateTime dateTime) {
        switch (type) {
            case YEAR:
                return lastDateTime(DateTimeType.DAY, dateTime).with(TemporalAdjusters.lastDayOfYear());
            case MONTH:
                return lastDateTime(DateTimeType.DAY, dateTime).with(TemporalAdjusters.lastDayOfMonth());
            case WEEK:
                return lastDateTime(DateTimeType.DAY, dateTime).with(DayOfWeek.SUNDAY);
            case DAY:
                return dateTime.toLocalDate().atTime(LocalTime.MAX);
            case HOUR:
                return lastDateTime(DateTimeType.MINUTE, dateTime).withMinute(59);
            case MINUTE:
                return lastDateTime(DateTimeType.SECOND, dateTime).withSecond(59);
            case SECOND:
                return dateTime.withNano(999_999_999);
            default:
                throw new IllegalStateException("Not supported value: " + type);
        }
    }

    public static List<LocalDateTime> getNodeDateTimes(DateTimeType type, String beginDateTime, String endDateTime) {
        return getNodeDateTimes(type, parse(type, beginDateTime), parse(type, endDateTime));
    }

    public static List<LocalDateTime> getNodeDateTimes(DateTimeType type,
                                                       LocalDateTime beginDateTime, LocalDateTime endDateTime) {
        return getNodeDateTimeEnds(
                fitDateTimeType(type, beginDateTime, endDateTime),
                firstDateTime(type, beginDateTime), lastDateTime(type, endDateTime));
    }

    @Deprecated
    protected static List<LocalDateTime> getNodeDateTimeBegins(final DateTimeType finalDateTimeType,
                                                               LocalDateTime beginDateTime, LocalDateTime endDateTime) {
        List<LocalDateTime> localDateTimes = new ArrayList<>();
        while (beginDateTime.isBefore(endDateTime)) {
            localDateTimes.add(beginDateTime);
            beginDateTime = plusOf(finalDateTimeType, beginDateTime);
        }
        return localDateTimes;
    }

    @Deprecated
    protected static List<LocalDateTime> getNodeDateTimeEndsOfInclude(final DateTimeType finalDateTimeType,
                                                                      LocalDateTime beginDateTime, LocalDateTime endDateTime) {
        List<LocalDateTime> nodeDateTimeEnds = getNodeDateTimeEnds(finalDateTimeType, beginDateTime, endDateTime);
        nodeDateTimeEnds.add(endDateTime);
        return nodeDateTimeEnds;
    }


    public static List<LocalDateTime> getNodeDateTimeEnds(final DateTimeType finalDateTimeType,
                                                          LocalDateTime beginDateTime, LocalDateTime endDateTime) {
        if (finalDateTimeType == null) {
            throw new IllegalArgumentException("Not allow type is null");
        }

        if (beginDateTime == null || endDateTime == null) {
            // 不可更改对象
            return Collections.emptyList();
        }

        return new ArrayList<LocalDateTime>() {
            //  可变的dynamicDateTime：dynamicDateTime被声明为volatile，表示多个线程可以同时访问和修改它。
            //  这可能会导致并发问题，因此在多线程环境下使用时需要谨慎。
            private LocalDateTime dynamicDateTime = beginDateTime;

            {
                while (dynamicDateTime.isBefore(endDateTime)) {
                    this.add(lastDateTime(finalDateTimeType, dynamicDateTime));
                    this.setDynamicDateTime(plusOf(finalDateTimeType, dynamicDateTime));
                }
            }

            void setDynamicDateTime(LocalDateTime dateTime) {
                this.dynamicDateTime = dateTime;
            }
        };
    }

    public static String fitPrintDateTime(DateTimeType prevType, DateTimeType nextType, LocalDateTime dateTime) {
        if (prevType == nextType) {
            return printDateTimeOfDefault(prevType, dateTime);
        } else {
            if (prevType == DateTimeType.WEEK && nextType == DateTimeType.DAY) {
                return parseOfCustomFormat(dateTime, prevType.getPastFormat());
            }
            return fitPrintDateTime(nextType, nextType, dateTime);
        }
    }

    public static String printDateTimeOfOpenIsNow(DateTimeType type, LocalDateTime lastDateTime) {
        final LocalDateTime nowLastDateTime = Dates.lastDateTime(type, LocalDateTime.now());
        if (lastDateTime.isBefore(nowLastDateTime)) {
            return printDateTimeOfOpen(type, lastDateTime);
        } else {
            if (lastDateTime.isEqual(nowLastDateTime)) {
                return type.getPresentFormat();
            }
        }
        return null;
    }

    public static String printDateTimeOfOpen(DateTimeType type, LocalDateTime dateTime) {
        if (type == DateTimeType.WEEK) {
            // 拦截，并重写了 WEEK 的执行逻辑
            int week = dateTime.get(WeekFields.ISO.weekOfWeekBasedYear());
            final LocalDateTime monday = firstDateTime(type, dateTime), sunday = lastDateTime(type, dateTime);
            if (monday.getYear() == sunday.getYear()) {
                return String.format("%s周(%s-%s)", week, parseOfCustomFormat(monday, "yyyy/M/d"),
                        parseOfCustomFormat(sunday, "M/d"));
            }
            return String.format("%s周(%s-%s)", week, parseOfCustomFormat(monday, "yyyy/M/d"),
                    parseOfCustomFormat(sunday, "yyyy/M/d"));
        }
        return printDateTimeOfDefault(type, dateTime);
    }

    protected static String printDateTimeOfDefault(DateTimeType type, LocalDateTime dateTime) {
        switch (type) {
            case YEAR:
                return dateTime.getYear() + type.getName();
            case MONTH:
                if (dateTime.getMonthValue() == 1) {
                    return parseOfCustomFormat(dateTime, "yyyy年M月");
                }
                return dateTime.getMonthValue() + type.getName();
            case WEEK:
                final LocalDateTime monday = firstDateTime(type, dateTime), sunday = lastDateTime(type, dateTime);
                if (monday.getYear() == sunday.getYear()) {
                    return String.format("%s/%s-%s/%s周",
                            monday.getMonthValue(), monday.getDayOfMonth(),
                            sunday.getMonthValue(), sunday.getDayOfMonth());
                } else {
                    return String.format("%s/%s/%s-%s/%s周",
                            monday.getYear(), monday.getMonthValue(), monday.getDayOfMonth(),
                            sunday.getMonthValue(), sunday.getDayOfMonth());
                }
            case DAY:
                if (dateTime.getMonthValue() == 1 && dateTime.getDayOfMonth() == 1) {
                    return parseOfCustomFormat(dateTime, "yyyy/M月d日");
                } else if (dateTime.getDayOfMonth() == 1) {
                    return parseOfCustomFormat(dateTime, "M月d日");
                } else {
                    return dateTime.getDayOfMonth() + type.getName();
                }
            case HOUR:
                if (dateTime.getMonthValue() == 1 && dateTime.getDayOfMonth() == 1) {
                    return parseOfCustomFormat(dateTime, "yyyy/M/d日H时");
                } else if (dateTime.getDayOfMonth() == 1 && dateTime.getHour() == 0) {
                    return parseOfCustomFormat(dateTime, "M/d日H时");
                } else if (dateTime.getHour() == 0) {
                    return parseOfCustomFormat(dateTime, "d日H时");
                } else {
                    return dateTime.getHour() + type.getName();
                }
            case MINUTE:
                if (dateTime.getMonthValue() == 1 && dateTime.getDayOfMonth() == 1 && dateTime.getHour() == 0
                        && dateTime.getMinute() == 0) {
                    return parseOfCustomFormat(dateTime, "yyyy/M月d日H点m分");
                } else if (dateTime.getDayOfMonth() == 1 && dateTime.getHour() == 0 && dateTime.getMinute() == 0) {
                    return parseOfCustomFormat(dateTime, "M月d日H点m分");
                } else if (dateTime.getHour() == 0 && dateTime.getMinute() == 0) {
                    return parseOfCustomFormat(dateTime, "d日H点m分");
                } else if (dateTime.getMinute() == 0) {
                    return parseOfCustomFormat(dateTime, "H点m分");
                } else {
                    return dateTime.getMinute() + type.getName();
                }
            case SECOND:
                if (dateTime.getMonthValue() == 1 && dateTime.getDayOfMonth() == 1 &&
                        dateTime.getHour() == 0 && dateTime.getMinute() == 0 && dateTime.getSecond() == 0) {
                    return parseOfCustomFormat(dateTime, "yyyy/M月d日H时m分s秒");
                } else if (dateTime.getDayOfMonth() == 1 && dateTime.getHour() == 0 && dateTime.getMinute() == 0
                        && dateTime.getSecond() == 0) {
                    return parseOfCustomFormat(dateTime, "M月d日H时m分s秒");
                } else if (dateTime.getHour() == 0 && dateTime.getMinute() == 0 && dateTime.getSecond() == 0) {
                    return parseOfCustomFormat(dateTime, "d日H时m分s秒");
                } else if (dateTime.getMinute() == 0 && dateTime.getSecond() == 0) {
                    return parseOfCustomFormat(dateTime, "H时m分s秒");
                } else if (dateTime.getSecond() == 0) {
                    return parseOfCustomFormat(dateTime, "m分s秒");
                } else {
                    return dateTime.getSecond() + type.getName();
                }
            default:
                throw new IllegalStateException("Not supported value: " + type);
        }
    }

}
